﻿using System;
using System.Reflection;
using T4UtilityBelt.Serializers;
using T4UtilityBelt.UxDesigns.Bindings.Parser.Parse;

namespace T4UtilityBelt.UxDesigns.Bindings
{
    public abstract class Bindable
    {
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")]
        protected readonly static MvxSwissBindingParser Parser = new MvxSwissBindingParser();

        //protected bool _hasValue;

        /// <summary>
        ///     Gets a value indicating whether the current <see cref="T:System.Nullable`1" /> object has a value.
        /// </summary>
        /// <returns>
        ///     true if the current <see cref="T:System.Nullable`1" /> object has a value; false if the current
        ///     <see
        ///         cref="T:System.Nullable`1" />
        ///     object has no value.
        /// </returns>
        public bool HasValue
        {
            get;
            protected set;
        }

        public Binding Binding { get; set; }
    }

    public class Bindable<T> : Bindable, IXAttributeSerializer, IXDefaultValue
    //where T : struct
    {
        private readonly T _value;

        /// <summary>
        ///     Initializes a new instance of the <see cref="T:System.Nullable`1" /> structure to the specified value.
        /// </summary>
        /// <param name="value">A value type.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
        public Bindable(T value)
        {
            if (value is string)
            {
                Parse(value as string);
            }
            else
            {
                _value = value;
                HasValue = true;
            }
        }

        public Bindable(string propertyName, BindingMode bindingMode)
        {
            Binding = new Binding(propertyName, bindingMode);
            HasValue = false;
        }

        public Bindable()
        {
            
        }


        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1307:SpecifyStringComparison", MessageId = "System.String.StartsWith(System.String)"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1307:SpecifyStringComparison", MessageId = "System.String.EndsWith(System.String)")]
        public void Parse(string value)
        {
            Binding binding;
            if (value != null
                && value.StartsWith("{")
                && value.EndsWith("}")
                && Parser.TryParseBindingDescription(value.Substring(1, value.Length - 2), out binding))
            {
                this.Binding = binding;
            }
            else
            {
                //_value = value as T;
                Type t = typeof(Bindable<T>);
                FieldInfo pi = t.GetField("_value", BindingFlags.NonPublic | BindingFlags.Instance);
                pi.SetValue(this, value);
                HasValue = true;
            }
        }


        /// <summary>
        ///     Gets the value of the current <see cref="T:System.Nullable`1" /> value.
        /// </summary>
        /// <returns>
        ///     The value of the current <see cref="T:System.Nullable`1" /> object if the <see cref="P:System.Nullable`1.HasValue" /> property is true. An exception is thrown if the
        ///     <see
        ///         cref="P:System.Nullable`1.HasValue" />
        ///     property is false.
        /// </returns>
        /// <exception cref="T:System.InvalidOperationException">
        ///     The <see cref="P:System.Nullable`1.HasValue" /> property is false.
        /// </exception>
        public T Value
        {
            get
            {
                if (!HasValue)
                {
                    throw new BindingException("Invalid Operation NoValue");
                }
                return _value;
            }
        }

        public override bool Equals(object obj)
        {
            if (HasValue)
            {
                if (obj != null)
                {
                    return _value.Equals(obj);
                }
                else
                {
                    return false;
                }
            }
            else
            {
                return obj == null;
            }
        }

        /// <summary>
        ///     Retrieves the hash code of the object returned by the <see cref="P:System.Nullable`1.Value" /> property.
        /// </summary>
        /// <returns>
        ///     The hash code of the object returned by the <see cref="P:System.Nullable`1.Value" /> property if the
        ///     <see cref="P:System.Nullable`1.HasValue" />
        ///     property is true, or zero if the
        ///     <see cref="P:System.Nullable`1.HasValue" />
        ///     property is false.
        /// </returns>
        /// <filterpriority>1</filterpriority>
        public override int GetHashCode()
        {
            if (HasValue)
            {
                return _value.GetHashCode();
            }
            else
            {
                return 0;
            }
        }

        /// <summary>
        ///     Retrieves the value of the current <see cref="T:System.Nullable`1" /> object, or the object's default value.
        /// </summary>
        /// <returns>
        ///     The value of the <see cref="P:System.Nullable`1.Value" /> property if the  <see cref="P:System.Nullable`1.HasValue" /> property is true; otherwise, the default value of the current
        ///     <see
        ///         cref="T:System.Nullable`1" />
        ///     object. The type of the default value is the type argument of the current
        ///     <see
        ///         cref="T:System.Nullable`1" />
        ///     object, and the value of the default value consists solely of binary zeroes.
        /// </returns>
        public T GetValueOrDefault()
        {
            return _value;
        }

        /// <summary>
        ///     Retrieves the value of the current <see cref="T:System.Nullable`1" /> object, or the specified default value.
        /// </summary>
        /// <returns>
        ///     The value of the <see cref="P:System.Nullable`1.Value" /> property if the <see cref="P:System.Nullable`1.HasValue" /> property is true; otherwise, the
        ///     <paramref
        ///         name="defaultValue" />
        ///     parameter.
        /// </returns>
        /// <param name="defaultValue">
        ///     A value to return if the <see cref="P:System.Nullable`1.HasValue" /> property is false.
        /// </param>
        public T GetValueOrDefault(T defaultValue)
        {
            if (HasValue)
            {
                return _value;
            }
            else
            {
                return defaultValue;
            }
        }

        public static explicit operator T(Bindable<T> value)
        {
            return value.Value;
        }

        //public static explicit operator Bindable<T>(string value)
        //{
        //    return new Bindable<T>(value);
        //}

        public static implicit operator Bindable<T>(T value)
        {
            return new Bindable<T>(value);
        }

        /// <summary>
        ///     Returns the text representation of the value of the current <see cref="T:System.Nullable`1" /> object.
        /// </summary>
        /// <returns>
        ///     The text representation of the value of the current <see cref="T:System.Nullable`1" /> object if the
        ///     <see
        ///         cref="P:System.Nullable`1.HasValue" />
        ///     property is true, or an empty string ("") if the
        ///     <see
        ///         cref="P:System.Nullable`1.HasValue" />
        ///     property is false.
        /// </returns>
        /// <filterpriority>1</filterpriority>
        public override string ToString()
        {
            if (HasValue)
            {
                return _value.ToString();
            }
            return null;
        }

        public string Serialize()
        {
            if (HasValue)
            {
                if (_value != null)
                {
                    return _value.ToString();
                }
                else
                {
                    return null;
                }
            }
            else
            {
                return "{" + Binding.Serialize() + "}";    
            }
        }

        public void Deserialize(string value)
        {
            Parse(value);
        }

        public bool CompareDefault(object defaultValue)
        {
            if (HasValue)
            {
                return Equals(defaultValue);
            }
            return false;
        }
    }
}